use core::ffi::c_void;

use alloc::{boxed::Box, sync::Arc};
use utils::MutCell;

use crate::{
    c_src::{
        osRtxThread_t, osThreadAttr_t, osThreadId_t, osThreadJoin, osThreadJoinable, osThreadNew,
    },
    osPriority_t,
};

use super::{DynamicMem, Error};

unsafe impl<T: Send + 'static> Send for Task<T> {}
unsafe impl<T: Send + 'static> Sync for Task<T> {}

const CB_SIZE: usize = size_of::<osRtxThread_t>();

pub struct Task<T: Send + 'static> {
    id: osThreadId_t,
    _cb_mem: DynamicMem,
    _stack_mem: DynamicMem,
    result: Arc<MutCell<Option<T>>>,
    join_flag: bool,
}

struct Args<T: Send + 'static, F: FnOnce() -> T + Send + 'static> {
    value: Arc<MutCell<Option<T>>>,
    f: F,
}

impl<T: Send + 'static> Drop for Task<T> {
    fn drop(&mut self) {
        if self.join_flag {
            return;
        }
        unsafe {
            osThreadJoin(self.id);
        }
    }
}

impl<T: Send + 'static> Task<T> {
    pub fn join(mut self) -> T {
        unsafe {
            osThreadJoin(self.id);
            self.join_flag = true;
            self.result.as_mut().take().expect("join value is none")
        }
    }

    pub fn spawn2<F: FnOnce() -> T + Send + 'static>(
        name: &'static str,
        stack_size: usize,
        priority: osPriority_t,
        f: F,
    ) -> Result<Self, Error> {
        let cb_mem = DynamicMem::new2(CB_SIZE)?;
        let stack_mem = DynamicMem::new2(stack_size)?;

        let value: Arc<MutCell<Option<T>>> = Arc::new(MutCell::const_new(None));
        let args = Box::new(Args {
            value: value.clone(),
            f,
        });

        extern "C" fn task_fn<T: Send + 'static, F: FnOnce() -> T + Send + 'static>(
            args: *mut c_void,
        ) {
            let arg = unsafe { Box::from_raw(args as *mut Args<T, F>) };
            let ret = (arg.f)();
            unsafe {
                *arg.value.as_mut() = Some(ret);
            }
        }

        let arg = Box::into_raw(args);
        let id = unsafe {
            let attr = osThreadAttr_t {
                name: name.as_ptr() as _,
                attr_bits: osThreadJoinable,
                cb_mem: cb_mem.ptr as _,
                cb_size: CB_SIZE as _,
                stack_mem: stack_mem.ptr as _,
                stack_size: stack_size as _,
                priority,
                tz_module: 0,
                reserved: 0,
            };
            osThreadNew(Some(task_fn::<T, F>), arg as _, &attr)
        };
        if id.is_null() {
            Err(Error::New)
        } else {
            Ok(Self {
                id,
                _cb_mem: cb_mem,
                _stack_mem: stack_mem,
                result: value,
                join_flag: false,
            })
        }
    }

    pub fn spawn<F: FnOnce() -> T + Send + 'static>(
        name: &'static str,
        stack_size: usize,
        priority: osPriority_t,
        f: F,
    ) -> Self {
        let cb_mem = DynamicMem::new(CB_SIZE);
        let stack_mem = DynamicMem::new(stack_size);

        let value: Arc<MutCell<Option<T>>> = Arc::new(MutCell::const_new(None));
        let args = Box::new(Args {
            value: value.clone(),
            f,
        });

        extern "C" fn task_fn<T: Send + 'static, F: FnOnce() -> T + Send + 'static>(
            args: *mut c_void,
        ) {
            let arg = unsafe { Box::from_raw(args as *mut Args<T, F>) };
            let ret = (arg.f)();
            unsafe {
                *arg.value.as_mut() = Some(ret);
            }
        }

        let arg = Box::into_raw(args);
        let id = unsafe {
            let attr = osThreadAttr_t {
                name: name.as_ptr() as _,
                attr_bits: osThreadJoinable,
                cb_mem: cb_mem.ptr as _,
                cb_size: CB_SIZE as _,
                stack_mem: stack_mem.ptr as _,
                stack_size: stack_size as _,
                priority,
                tz_module: 0,
                reserved: 0,
            };
            osThreadNew(Some(task_fn::<T, F>), arg as _, &attr)
        };
        if id.is_null() {
            panic!("task new fail");
        }

        Self {
            id,
            _cb_mem: cb_mem,
            _stack_mem: stack_mem,
            result: value,
            join_flag: false,
        }
    }
}
